package com.lin.benchmade;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lin.benchmade.pinyin.LetterSpliter;
import com.lin.benchmade.pinyin.PinYinBean;
import com.lin.benchmade.pinyin.PinYinConverter;
import com.lin.benchmade.pinyin.PinyinIndexDictionary;
import com.lin.benchmade.ternary.TernarySearchTrie;
import com.lin.benchmade.ternary.TernarySearchTrie.TSTNode;
import com.lin.benchmade.ts.JChineseConvertor;
import com.lin.benchmade.utils.ConfigReader;
import com.lin.benchmade.utils.Hit;
import com.lin.benchmade.utils.HitPriorityQueue;
import com.lin.benchmade.utils.TypeTokenizer;

/**
 * 搜索提示入口. 
 * @author xiaolin
 */
public class Benchmade {

	private final Logger LOG = LoggerFactory.getLogger(Benchmade.class);
	private TernarySearchTrie mainTernaryTrie = new TernarySearchTrie();
	private PinyinIndexDictionary mainInOrderPYP = new PinyinIndexDictionary();
	private TernarySearchTrie bakTernaryTrie = new TernarySearchTrie();
	private PinyinIndexDictionary bakInOrderPYP = new PinyinIndexDictionary();
	private final JChineseConvertor chineseConvertor = JChineseConvertor.getInstance();
	private static Benchmade instance = new Benchmade();
	private short dicItemLimit;
	private short showNum = 10;
	private boolean lock = true;// 更新锁
	private final LetterSpliter pys = new LetterSpliter();

	private Benchmade() {
		mainTernaryTrie.setMatchAlmostDiff(0);
		dicItemLimit = Short.parseShort(ConfigReader.getEntry("dicItemLimit"));
		showNum = Short.parseShort(ConfigReader.getEntry("showNum"));
		long st = System.currentTimeMillis();
		initData();
		long et = System.currentTimeMillis();
		LOG.info("Initialization processed in : "+ (et - st) + "ms");
	}

	public static synchronized Benchmade getInstance() {
		return instance;
	}

	public void initData() {
		try {
			FileInputStream fis = new FileInputStream(ConfigReader.getEntry("sourcepath"));
			InputStreamReader isr = new InputStreamReader(fis, "utf-8");
			BufferedReader br = new BufferedReader(isr);

			int num = 0;
			String line;
			int boost;
			char[] simplified_CN_cArr;
			char[] traditionalCN_cArr;
			String[] pinyinArray;
			while ((line = br.readLine()) != null) {
				String[] lineArray = StringUtils.split(line, "\t");
				if (lineArray.length == 2) {
					boost = Integer.parseInt(lineArray[1]);
					//if (boost > 100F) {
					simplified_CN_cArr = chineseConvertor.t2s(lineArray[0]).toCharArray();
					traditionalCN_cArr = new char[simplified_CN_cArr.length];
					pinyinArray = PinYinConverter.INSTANCE.encodeFillAndToPinyin(simplified_CN_cArr, traditionalCN_cArr);
					mainInOrderPYP.addItem(pinyinArray, traditionalCN_cArr);
					//pys.add(pinyinArray);
					mainTernaryTrie.put(new String(traditionalCN_cArr), boost);
					num++;
					if (num % 100000 == 0) {
						LOG.info("building...." + num);
					}
					//}
				}
			}

			LOG.info("A total of " + num + " line add to Trie");

			br.close();
			isr.close();
			fis.close();

			mainInOrderPYP.converterAndSort();

			//LOG.info("init success" + mainTernaryTrie.numNodes());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public synchronized void rebuild() {
		bakInOrderPYP.converterAndSort();
		lock = false;

		mainTernaryTrie = bakTernaryTrie;
		bakTernaryTrie = new TernarySearchTrie();

		mainInOrderPYP = bakInOrderPYP;
		bakInOrderPYP = new PinyinIndexDictionary();

		lock = true;
	}

	public synchronized void append(char[] key, int score, String[] pinyinArray) {
		bakInOrderPYP.addItem(pinyinArray, key);
		bakTernaryTrie.put(new String(key), score);
	}

	public List<Hit> autocomplete(String keyWord) {
		return autocomplete(keyWord, showNum);
	}

	/**
	 * 对外几口方法
	 * 
	 * @param keyWord
	 *            用户输入的关键字
	 * @return 返回提示词
	 */
	private List<Hit> autocomplete(String keyWord, int num) { 
		long startTime = System.currentTimeMillis();
		List<Hit> resultHit = new ArrayList<Hit>(num);
		if (lock) {
			HitPriorityQueue hpq = new HitPriorityQueue(num);
			Set<String> descartes = descartesMinGranularMatch(keyWord);
			for (String str : descartes) {
				List<Hit> pror = mainTernaryTrie.matchPrefixHit(str);
				for (Hit h : pror) {
					hpq.insertWithOverflow(h);
				}
			}
			int size = hpq.size();
			for (short i = 0; i < size; i++) {
				Hit hit = hpq.pop();
				validation(hit);
				resultHit.add(hit);
			}
			long endTime = System.currentTimeMillis();
			//LOG.info(keyWord + "  time: " + (endTime - startTime) + "  descartes:" + descartes.size());
		}
		return resultHit;
	}

	/**
	 * 合并一个set和一个String，取set和一个String的笛卡尔积 set是前缀，Stirng一般是汉字和数字.
	 * 
	 * @param prefix
	 *            前缀的笛卡尔积序列
	 * @param suffix
	 *            后缀的一个String
	 * @return
	 */
	private Set<String> mergeWord(Set<String> prefix, String suffix) {
		Set<String> result = new HashSet<String>();
		if (prefix.isEmpty()) {
			result.add(suffix);
		} else {
			TSTNode prefixNode;
			for (String pf : prefix) {
				if ((prefixNode = mainTernaryTrie.getNode(pf)) == null) {// 如果一个前缀的笛卡尔积序列在三叉树中根本就没有，就不用考虑以它为头的字符串序列
					continue;
				}
				if (mainTernaryTrie.contains(suffix, prefixNode)) {
					result.add(pf + suffix);
				}
			}
		}
		return result;
	}

	/**
	 * 2个Set取笛卡尔积，用2个for循环取得前缀笛卡尔积序列和一个后缀char的笛卡尔积序列.
	 * @param prefix	 前缀笛卡尔积序列
	 * @param suffix	后缀的序列
	 * @return
	 * modified by xiaolin  date:2012-11-22 
	 * 之前每次用tst验证前缀节点,前缀合格后添加后缀,下次再验证组合后的String,当做前缀来再次验证.
	 * 调整为每次搜索验证parent,然后从parentnode开始验证子节点.保证每次merge后的数据都是合法数据
	 */
	private Set<String> mergeLetter(Set<String> prefix, List<PinyinIndexDictionary.Node> suffix, String pinyin, int itemThreshold) {
		if (suffix == null) {
			suffix = new ArrayList<PinyinIndexDictionary.Node>(0);
		}
		itemThreshold = itemThreshold < 0 || itemThreshold > suffix.size() ? suffix.size() : itemThreshold;
		Set<String> result = new HashSet<String>();
		TSTNode prefixNode;
		for (String pf : prefix) {
			//每次取出前缀的node.如果这个node不为null,进行循环.
			if ((prefixNode = mainTernaryTrie.getNode(pf)) == null) {// 如果一个前缀的笛卡尔积序列在三叉树中根本就没有，就不用考虑以它为头的字符串序列
				continue;
			}

			//从树中父节点开始搜索,只需要对子节点的char进行匹配.不需要再从头匹配,
			for (int i = 0; i < itemThreshold; i++) {
				if (mainTernaryTrie.contains(String.valueOf(suffix.get(i).getValue()), prefixNode)) {
					result.add(pf + suffix.get(i).getValue());
				}
			}
			if (mainTernaryTrie.contains(pinyin, prefixNode)) {
				result.add(pf + pinyin);
			}// 在循环最后加上一个pinyin，不管是全拼还是简拼，为了防止“实战lucene”
		}
		return result;
	}

	// 还原繁体字为简体字
	private void validation(Hit hit) {
		hit.setValue(PinYinConverter.INSTANCE.traditionalChineseDecode(hit.getValue()));
	}

	public Set<String> descartesMinGranularMatch(String keyWord) {
		Set<String> retult = new HashSet<String>();
		keyWord = chineseConvertor.t2s(keyWord);
		if (keyWord.length() == 1) {// 如果就一个关键字，直接返回
			retult.add(keyWord);
			return retult;
		}
		Set<String> prefixSet = new HashSet<String>(256);// 前缀匹配的集合
		// 用分词按类型区分一下，英文字母类型是2，中文是5，数字是9.这是Character.getType(c)转换为int时得到的
		TypeTokenizer tokenizer = new TypeTokenizer(new StringReader(keyWord));
		TypeAttribute typeAtt = tokenizer.getAttribute(TypeAttribute.class);
		CharTermAttribute chartermAtt = tokenizer.getAttribute(CharTermAttribute.class);

		try {
			while (tokenizer.incrementToken()) {
				// 分词后得到的类型和Term值
				if (typeAtt.type().equals("2")) { // 如果是拼音字母
					List<LetterSpliter.BuildingBlock> sp = pys.split(chartermAtt.toString()); // 切分
					Set<String> parentPrefixSet = new HashSet<String>(128);
					for (LetterSpliter.BuildingBlock pst : sp) {
						parentPrefixSet.addAll(onceCartesianProduct(pst, prefixSet));
					}
					prefixSet = parentPrefixSet;
				} else {
					prefixSet = mergeWord(prefixSet, chartermAtt.toString());// 合并set和一个string
				}
				/**
				 * add 2012-11-16 如果前缀都是空的,那么就没有必要往下走,直接终止.
				 */
				if (prefixSet.isEmpty()) {
					break;
				}
			}

		} catch (IOException e) {
			e.printStackTrace();
			return retult;
		}
		retult.addAll(prefixSet);
		prefixSet = null;
		try {
			tokenizer.close();
		} catch (IOException e) {
			LOG.error(e.getMessage(), e);
		}
		return retult;
	}

	/**
	 * 建立以下一行序列的笛卡尔积。
	 * 获取笛卡尔积，根据不同的类型（拼音、汉字、数字）获取他们的笛卡尔积，用笛卡尔积的条目去三叉树中匹配
	 * 因为关键字拆分后的长度是不确定的，所以没办法用固定个for循环取得。所以思路是一个字符串“doupo斗破123”
	 * 先用分词取出"doupo" 然后拆分拼音，拼音信息包含拆分结果和类型（见PinYinBean）,取得dou的所有汉字
	 * 如果prefixSet是空的，那么先用dou的所有汉字填充，第二次for循环取得po的所有汉字。
	 * 用2个set取得笛卡尔积，然后再用这个笛卡尔积和下一个Set或者String去笛卡尔积
	 * 这样把多个for循环，拆成一定是2个的for循.
	 * jia n guo 
	 * jian gu o 
	 * jian guo
	 * @param pst
	 * @return
	 */
	private Set<String> onceCartesianProduct(LetterSpliter.BuildingBlock pinyinList, Set<String> profixCombined) {
		String pinyinValue;
		Set<String> productCollection = new HashSet<String>();
		productCollection.addAll(profixCombined);
		int pyListSize = pinyinList.size();
		ArrayList<PinYinBean> data = pinyinList.getData();

		for (int cursor = 0; cursor < pyListSize; cursor++) {
			pinyinValue = new String(data.get(cursor).getPinyin());
			List<PinyinIndexDictionary.Node> pinyin2CNItems;
			if (!data.get(cursor).isComplete() || cursor == pyListSize - 1) {
				pinyin2CNItems = mainInOrderPYP.matchPerfix(pinyinValue);
			} else {
				pinyin2CNItems = mainInOrderPYP.match(pinyinValue);
			}

			if (productCollection.isEmpty() && cursor == 0) {// 如果笛卡尔积的的set是空的，就不需要合并先填充
				if (pinyin2CNItems != null) {
					int end = pinyin2CNItems.size() > dicItemLimit ? dicItemLimit : pinyin2CNItems.size();
					for (int i = 0; i < end; i++) {// 因为得到的是Character，不能直接赋值给Set<String>，所以循环赋值
						productCollection.add(Character.toString(pinyin2CNItems.get(i).getValue()));
					}
				}
				productCollection.add(pinyinValue);// add：2012-06-29
				// vivi这样的词在字典中匹配不到数据，是null的上面的if(prefixChar!=null){也是今天添加的。见今天的readme
			} else {
				/* private Set<String> merge(Set<String> prefix,Set<Character> suffix) 2012-06-23 修改为新方法，添加一个
				 * PinYinBean，防止汉字后面后英文，英文正好又能拼接成一个全拼。
				 * 为什么不在上面的方法中添加一个参数。因为如果是null，方法头上已经添加了*/
				productCollection = mergeLetter(productCollection, pinyin2CNItems, pinyinValue, dicItemLimit = cursor == pyListSize - 1 ? 200
						: dicItemLimit);// 如果set不是空，那么将2个set合并,如果是最后一个字了，那么limit放宽一点，增加到200
				/*
				 * add 2012/11/16 
				 *  如果一个BuildingBlock中的pinyin 已经不是第一个开始,并且前缀的集合是空的,那么就没有必要再对后面的拼音进行组装了
				 *  因为添加了多种拼音组合返回,有一部分应该是非法的.所以要尽可能的提前抛弃一些不符合的序列.
				 */
				if (productCollection.isEmpty() && cursor > 0) {
					break;
				}
			}
			pinyin2CNItems = null;
		}
		StringBuilder sb = new StringBuilder();
		for (String s : productCollection) {
			sb.append(s).append(", ");
		}
		LOG.debug(sb.toString());
		return productCollection;
	}

	public static void main(String[] args) throws IOException {
		Benchmade c = Benchmade.getInstance();
		List<Hit> hit = c.autocomplete("jianguo");
		for (Hit h : hit) {
			System.out.println(h);
		}
	}

}
